
mod ffi;
mod window;

pub mod util;
pub mod ntdll;
pub mod error;
pub mod disasm;
#[cfg(feature="std")]
pub mod inject;
pub mod symbol;
pub mod string;
#[cfg(feature="std")]
pub mod hook;
#[cfg(feature="dbglog")]
pub mod dbglog;

pub use self::window::*;
pub use self::util::*;
pub use self::symbol::SymbolApi;

use core::slice::{from_raw_parts, from_raw_parts_mut};
use core::mem::{zeroed, transmute, size_of, size_of_val};
use core::ptr::{null, null_mut};
use core::ffi::c_void;
use alloc::sync::Arc;
use alloc::string::String;

use winapi::um::tlhelp32::*;
use winapi::um::memoryapi::*;
use winapi::um::psapi::*;
use winapi::um::fileapi::*;
use winapi::um::winuser::*;
use winapi::um::handleapi::*;
use winapi::um::processthreadsapi::*;
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::debugapi::OutputDebugStringW;
use winapi::um::libloaderapi::GetProcAddress;
use winapi::shared::ntdef::UNICODE_STRING;
use winapi::um::dbghelp::*;
pub use winapi::shared::windef::*;
pub use ntapi;
pub use winapi;

use serde::{Deserialize, Serialize};
// use ntapi::ntpebteb::PEB;
use ntapi::ntpsapi::PROCESS_BASIC_INFORMATION;

use crate::*;
use super::ntdll::*;

pub type pid_t = u32;

pub trait NtHeader {
    fn as_32(&self) -> &IMAGE_NT_HEADERS32;
    fn as_64(&self) -> &IMAGE_NT_HEADERS64;
    fn is_32(&self) -> bool;
}

impl NtHeader for IMAGE_NT_HEADERS {
    #[inline]
    fn as_32(&self) -> &IMAGE_NT_HEADERS32 { unsafe { transmute(self) } }
    #[inline]
    fn as_64(&self) -> &IMAGE_NT_HEADERS64  { unsafe { transmute(self) } }
    #[inline]
    fn is_32(&self) -> bool { self.FileHeader.Machine == IMAGE_FILE_MACHINE_I386 }
}

#[derive(Deref)]
pub struct Handle(HANDLE);

unsafe impl Send for Handle {}

impl Handle {
    #[inline(always)]
    pub fn is_valid(&self) -> bool { self.0 != INVALID_HANDLE_VALUE }

    #[inline]
    pub fn success(&self) -> bool { self.is_valid() && !self.is_null() }

    #[inline(always)]
    pub unsafe fn from_raw_handle(handle: HANDLE) -> Self {
        Self(handle)
    }

    pub unsafe fn clone_from_raw(handle: HANDLE) -> Option<Self> {
        let mut result = null_mut();
        if DuplicateHandle(
            GetCurrentProcess(),
            handle,
            GetCurrentProcess(),
            &mut result,
            0, 0, DUPLICATE_SAME_ACCESS
        ) > 0 { Some(Self::from_raw_handle(result)) } else { None }
    }

    #[inline(always)]
    pub fn clone(&self) -> Option<Self> {
        unsafe {
            Self::clone_from_raw(self.0)
        }
    }
}

impl Clone for Handle {
    fn clone(&self) -> Self {
        Handle::clone(self).expect("clone")
    }
}

impl Drop for Handle {
    fn drop(&mut self) { unsafe { CloseHandle(self.0); } }
}

#[derive(Debug)]
pub enum WmError {
    Win32(DWORD),
    Reason(&'static str),
    Failure,
    DisAsm,
    ReadMemory,
    CreateFile,
    LoadLibrary,
    VirtualAlloc,
    GetProcAddress,
    WindowNotFound,
    ThreadNotFound,
}

impl WmError {
    #[inline]
    pub fn last() -> WmError { WmError::Win32(unsafe { GetLastError() }) }

    #[inline]
    pub fn last_result<T>() -> Result<T, WmError> { Err(Self::last()) }
}

type ToolHelper<T> = unsafe extern "system" fn(HANDLE, *mut T) -> BOOL;

pub struct ToolHelperIter<T: Copy> {
    count: u32,
    handle: Handle,
    data: T,
    f_first: ToolHelper<T>,
    f_next: ToolHelper<T>,
}

impl<T: Copy> ToolHelperIter<T> {
    fn new(handle: HANDLE, data: T, f_first: ToolHelper<T>, f_next: ToolHelper<T>) -> ToolHelperIter<T> {
        // assert!(handle != INVALID_HANDLE_VALUE);
        let handle = unsafe { Handle::from_raw_handle(handle) };
        ToolHelperIter { handle, count: 0, data, f_first, f_next }
    }

    fn next_item(&mut self) -> bool {
        let success = unsafe {
            if self.count > 0 {
                (self.f_next)(*self.handle, &mut self.data) > 0
            } else {
                (self.f_first)(*self.handle, &mut self.data) > 0
            }
        };
        self.count += 1;
        return success;
    }
}

impl<T: Copy> Iterator for ToolHelperIter<T> {
    type Item = T;

    fn next(&mut self) -> Option<T> {
        if self.next_item() { Some(self.data) } else { None }
    }
}

pub trait ThreadInfo {
    fn pid(&self) -> u32;
    fn tid(&self) -> u32;
}

impl ThreadInfo for THREADENTRY32 {
    #[inline]
    fn pid(&self) -> u32 { self.th32OwnerProcessID }
    #[inline]
    fn tid(&self) -> u32 { self.th32ThreadID }
}

pub fn enum_thread() -> ToolHelperIter<THREADENTRY32> {
    unsafe {
        let mut te32: THREADENTRY32 = zeroed();
        te32.dwSize = size_of_val(&te32) as u32;
        ToolHelperIter::new(CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0).into(), te32, Thread32First, Thread32Next)
    }
}

pub trait ModuleInfo {
    fn name(&self) -> Arc<str>;
    fn path(&self) -> Arc<str>;
    fn base(&self) -> usize;
    fn size(&self) -> usize;
    fn id(&self) -> u32;
}

impl ModuleInfo for MODULEENTRY32W {
    fn name(&self) -> Arc<str> { self.szModule.as_ref().to_utf8().into() }
    fn path(&self) -> Arc<str> { self.szExePath.as_ref().to_utf8().into() }
    fn base(&self) -> usize { self.modBaseAddr as usize }
    fn size(&self) -> usize { self.modBaseSize as usize }
    fn id(&self) -> u32 { self.th32ModuleID }
}

pub trait ProcessInfo {
    fn pid(&self) -> u32;
    fn name(&self) -> String;
}

impl ProcessInfo for PROCESSENTRY32W {
    #[inline]
    fn pid(&self) -> u32 { self.th32ProcessID }
    #[inline]
    fn name(&self) -> String { self.szExeFile.as_ref().to_utf8() }
}

pub fn enum_process() -> ToolHelperIter<PROCESSENTRY32W> {
    unsafe {
        let mut pe32: PROCESSENTRY32W = zeroed();
        pe32.dwSize = size_of_val(&pe32) as u32;
        ToolHelperIter::new(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0), pe32, Process32FirstW, Process32NextW)
    }
}

#[inline]
pub fn enum_process_filter_name(name: &str) -> impl Iterator<Item = PROCESSENTRY32W> + '_ {
    enum_process().filter(move |p| p.name().eq_ignore_ascii_case(name))
}

pub fn get_thread_context(tid: u32, context: &mut CONTEXT, flags: u32) -> bool {
    let handle = open_thread(tid, THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, false);
    unsafe {
        context.ContextFlags = flags;
        SuspendThread(handle.0);
        let r = GetThreadContext(handle.0, context);
        ResumeThread(handle.0);
        return r > 0;
    }
}

pub fn enum_module(pid: u32) -> ToolHelperIter<MODULEENTRY32W> {
    unsafe {
        let mut te32: MODULEENTRY32W = zeroed();
        te32.dwSize = size_of_val(&te32) as u32;
        ToolHelperIter::new(CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid), te32, Module32FirstW, Module32NextW)
    }
}

#[derive(Serialize, Deserialize)]
pub struct MemoryPage {
    pub base: usize,
    pub alloc_base: usize,
    pub size: usize,
    pub type_: u32,
    pub state: u32,
    pub protect: u32,
    pub alloc_protect: u32,
}

impl MemoryPage {
    pub fn from_mbi(mbi: &MEMORY_BASIC_INFORMATION) -> MemoryPage {
        MemoryPage {
            base: mbi.BaseAddress as usize,
            alloc_base: mbi.AllocationBase as usize,
            size: mbi.RegionSize,
            type_: mbi.Type,
            state: mbi.State,
            protect: mbi.Protect,
            alloc_protect: mbi.AllocationProtect,
        }
    }

    #[inline]
    pub fn is_commit(&self) -> bool { self.state & MEM_COMMIT > 0 }

    #[inline]
    pub fn is_reserve(&self) -> bool { self.state & MEM_RESERVE > 0 }

    #[inline]
    pub fn is_free(&self) -> bool { self.state & MEM_FREE > 0 }

    #[inline]
    pub fn is_private(&self) -> bool { self.type_ & MEM_PRIVATE > 0 }

    pub fn is_executable(&self) -> bool { self.protect & 0xF0 > 0 }
    pub fn is_writable(&self) -> bool { self.protect & 0xCC > 0 }
    pub fn is_readonly(&self) -> bool { self.protect == PAGE_READONLY }

    pub fn protect(&self) -> String {
        let guard = self.protect & PAGE_GUARD > 0;
        let mut result = match self.protect & !PAGE_GUARD {
            PAGE_NOACCESS => "-----",
            PAGE_READONLY => "-R---",
            PAGE_READWRITE => "-RW--",
            PAGE_WRITECOPY => "-RWC-",
            PAGE_EXECUTE => "E----",
            PAGE_EXECUTE_READ => "ER---",
            PAGE_EXECUTE_READWRITE => "ERW--",
            PAGE_EXECUTE_WRITECOPY => "ERWC-",
            _ => "?????",
        }.to_string();
        if guard {
            unsafe {
                result.as_bytes_mut()[4] = b'G';
            }
        }
        result
    }

    pub fn type_(&self) -> &'static str {
        match self.type_ {
            MEM_PRIVATE => "PRV",
            MEM_IMAGE => "IMG",
            MEM_MAPPED => "MAP",
            _ => ""
        }
    }
}

pub trait UnicodeUtil {
    fn size(&self) -> u16;
    fn max_size(&self) -> u16;
    fn to_string(&self) -> String;
    fn as_slice(&self) -> Option<&[u16]>;
    fn as_mut_slice(&mut self) -> Option<&mut [u16]>;
    fn as_slice_with_null(&self) -> Option<&[u16]>;
}

impl UnicodeUtil for UNICODE_STRING {
    fn size(&self) -> u16 { self.Length }
    fn max_size(&self) -> u16 { self.MaximumLength }

    fn to_string(&self) -> String {
        unsafe {
            from_raw_parts(self.Buffer, self.Length as usize).to_utf8()
        }
    }

    fn as_mut_slice(&mut self) -> Option<&mut [u16]> {
        unsafe {
            if self.Buffer.is_null() { return None; }
            Some(from_raw_parts_mut(self.Buffer, self.size() as usize))
        }
    }

    fn as_slice(&self) -> Option<&[u16]> {
        if self.Buffer.is_null() { return None; }
        Some(unsafe { from_raw_parts(self.Buffer, self.size() as usize) })
    }

    fn as_slice_with_null(&self) -> Option<&[u16]> {
        if self.Buffer.is_null() { return None; }
        Some(unsafe { from_raw_parts(self.Buffer, self.size() as usize + 1) })
    }
}

#[repr(C)]
pub struct ExceptionRecord {
    pub code: u32,
    pub flags: u32,
    pub record: u64,
    pub address: u64,
    pub param_num: u32,
    pub params: [u64; EXCEPTION_MAXIMUM_PARAMETERS],
}

impl ExceptionRecord {
    pub fn copy(&mut self, r: &EXCEPTION_RECORD) {
        self.code = r.ExceptionCode;
        self.flags = r.ExceptionFlags;
        self.record = r.ExceptionRecord as u64;
        self.address = r.ExceptionAddress as u64;
        self.param_num = r.NumberParameters;
        for i in 0..r.NumberParameters as usize {
            self.params[i] = r.ExceptionInformation[i] as u64;
        }
    }
}

pub const SIZE_OF_CALL: usize = 5;
pub const MAX_INSN_SIZE: usize = 16;

pub fn read_process_memory(handle: HANDLE, address: usize, data: &mut [u8]) -> usize {
    let mut readed = 0usize;
    let address = address as LPVOID;
    let pdata = data.as_mut_ptr() as LPVOID;
    unsafe {
        if ReadProcessMemory(handle, address, pdata, data.len(), &mut readed) > 0 {
            readed
        } else { 0usize }
    }
}

pub fn write_process_memory(handle: HANDLE, address: usize, data: &[u8]) -> usize {
    let mut written = 0usize;
    let mut old_protect = 0u32;
    let mut new_protect = 0u32;
    let address = address as LPVOID;
    unsafe {
        VirtualProtectEx(handle, address, data.len(), PAGE_EXECUTE_READWRITE, &mut old_protect);
        let result = WriteProcessMemory(
                handle, address, data.as_ptr() as LPVOID, data.len(), &mut written);
        VirtualProtectEx(handle, address, data.len(), old_protect, &mut new_protect);
        if result > 0 { written } else { 0usize }
    }
}

#[derive(Clone)]
pub struct Process {
    pub pid: u32,
    pub handle: Handle,
}

pub enum DumpType {
    Mini,
    Full
}

pub struct MemoryIter<'p> {
    pub process: &'p Process,
    pub address: usize,
}

impl MemoryIter<'_> {
    pub fn next_commit(&mut self) -> Option<MemoryPage> {
        while let Some(m) = self.next() {
            if m.is_commit() { return Some(m); }
        }
        return None;
    }
}

impl Iterator for MemoryIter<'_> {
    type Item = MemoryPage;

    fn next(&mut self) -> Option<Self::Item> {
        let result = self.process.virtual_query(self.address);
        if let Some(m) = result.as_ref() { self.address += m.size; }
        return result;
    }
}

impl ReadMemory for Process {
    fn read_memory<'a>(&self, addr: Address, data: &'a mut [u8]) -> Option<&'a mut [u8]> {
        let r = read_process_memory(*self.handle, addr, data);
        if r > 0 { Some(&mut data[..r]) } else { None }
    }
}

impl WriteMemory for Process {
    fn write_memory(&self, address: Address, data: &[u8]) -> Option<usize> {
        let r = self.write_memory(address, data);
        if r > 0 { Some(r) } else { None }
    }
}

pub trait ProcessInfoExt {
    fn get_mapped_file_name(&self, address: usize) -> Option<String>;
    fn get_peb(&self) -> usize;
}

impl ProcessInfoExt for Process {
    // https://docs.microsoft.com/zh-cn/windows/win32/memory/obtaining-a-file-name-from-a-file-handle
    fn get_mapped_file_name(&self, address: usize) -> Option<String> {
        unsafe {
            let mut buf = [0u16; 300];
            let len = GetMappedFileNameW(*self.handle, address as LPVOID, buf.as_mut_ptr(), buf.len() as u32);
            if len > 0 { util::to_dos_path(&mut buf).map(|s| s.to_utf8()).or_else(|| Some(buf.as_ref().to_utf8())) } else { None }
        }
    }

    fn get_peb(&self) -> usize {
        self.basic_information().map(|p| p.PebBaseAddress as usize).unwrap_or(0)
    }
}

pub fn get_window(pid: pid_t) -> Option<HWND> {
    let mut w = null_mut();
    enum_process_window(pid, |hwnd| { w = hwnd; !w.is_visible() });
    if w.is_null() { None } else { Some(w) }
}

pub fn duplicate_process(pid: u32, access: u32) -> impl Iterator<Item=Handle> {
    find_handle(7, access).filter_map(move |h| unsafe {
        if h.pid() < 5 { return None; }
        let p = Handle(OpenProcess(PROCESS_DUP_HANDLE, 0, h.pid()));
        if p.is_null() {
            // error!("OpenProcess {} failed {}", h.pid(), get_last_error_string());
            return None;
        }
        // println!("Handle {:x} Access {:x} Pid {}", h.HandleValue, h.GrantedAccess, h.pid());

        let mut target: HANDLE = null_mut();
        if DuplicateHandle(
            p.0,
            h.HandleValue as HANDLE,
            GetCurrentProcess(),
            &mut target,
            0, 0, DUPLICATE_SAME_ACCESS
        ) == 0 || target.is_null() || pid != GetProcessId(target) {
            CloseHandle(target); None
        } else { Some(Handle::from_raw_handle(target)) }
    })
}

impl Process {
    pub fn open(pid: u32, access: Option<u32>) -> Option<Process> {
        unsafe {
            let handle = OpenProcess(access.unwrap_or(PROCESS_ALL_ACCESS), 0, pid);
            if handle.is_null() {
                None
            } else {
                Process::from_handle(Handle::from_raw_handle(handle))
            }
        }
    }

    pub fn duplicate_from_other_process(pid: u32, access: u32) -> Result<Process, WmError> {
        let handle = duplicate_process(pid, access).next().ok_or(WmError::Reason("dup not found"))?;
        Self::from_handle(handle).ok_or_else(WmError::last)
    }

    pub fn from_name(name: &str, access: Option<u32>) -> Result<Process, WmError> {
        let pid = enum_process_filter_name(name).next().ok_or(WmError::Reason("name not found"))?.pid();
        Self::open(pid, access).ok_or_else(WmError::last)
    }

    pub fn from_handle(handle: Handle) -> Option<Process> {
        unsafe {
            let pid = GetProcessId(*handle);
            if pid == 0 { return None; }

            return Some(Process { pid, handle });
        }
    }

    pub(crate) fn current() -> Process {
        unsafe {
            Self::from_handle(Handle::from_raw_handle(GetCurrentProcess())).unwrap()
        }
    }

    pub fn basic_information(&self) -> Option<PROCESS_BASIC_INFORMATION> {
        query_process(*self.handle, ProcessInfoClass::BasicInformation, None)
    }

    pub fn peb(&self) -> Option<usize> { self.basic_information().map(|i| i.PebBaseAddress as usize) }

    // https://docs.microsoft.com/en-us/windows/win32/api/wow64apiset/nf-wow64apiset-iswow64process
    pub fn is_wow64(&self) -> bool {
        use winapi::um::wow64apiset::IsWow64Process;
        let mut result: BOOL = 0;
        unsafe { IsWow64Process(*self.handle, &mut result); };
        return result != 0;
    }

    pub fn get_module_name(&self, module: u64) -> Result<String, WmError> {
        unsafe {
            let mut name = [0 as u16; MAX_PATH];
            if GetModuleBaseNameW(*self.handle, module as HMODULE, name.as_mut_ptr(), MAX_PATH as u32) > 0 {
                Ok(name.as_ref().to_utf8())
            } else { WmError::last_result() }
        }
    }

    // TODO: [bug] wow64进程下32位dll取到的是64位的路径
    #[deprecated]
    pub fn get_module_path(&self, module: usize) -> Option<String> {
        unsafe {
            let mut path = [0 as u16; MAX_PATH];
            if GetModuleFileNameExW(*self.handle, module as HMODULE, path.as_mut_ptr(), MAX_PATH as u32) > 0 {
                Some(path.as_ref().to_utf8())
            } else { None }
        }
    }

    /// use EnumProcessModulesEx
    pub fn get_module_list(&self, flag: u32) -> Option<Vec<usize>> {
        unsafe {
            let mut len = 0u32;
            EnumProcessModulesEx(self.handle.0, null_mut(), 0, &mut len, flag);
            let mut result = vec![0usize; len as usize];
            if len > 0 {
                if EnumProcessModulesEx(
                    self.handle.0, transmute(result.as_mut_ptr()),
                    result.len() as u32, &mut len, flag
                ) > 0 {
                    return Some(result.into_iter().filter(|&m| m > 0).collect());
                }
            }
            None
        }
    }

    /// use GetModuleInformation
    pub fn get_module_info(&self, base: usize) -> Option<MODULEINFO> {
        unsafe {
            let mut result: MODULEINFO = zeroed();
            if GetModuleInformation(
                self.handle.0, transmute(base),
                &mut result, size_of::<MODULEINFO>() as u32
            ) > 0 { return Some(result); }
            None
        }
    }

    pub fn duplicate_handle(&self, src_handle: HANDLE, dst_ps: HANDLE) -> Option<HANDLE> {
        let mut handle: HANDLE = null_mut();
        unsafe {
            if 0 != DuplicateHandle(self.handle.0, src_handle, dst_ps, &mut handle, 0, FALSE, DUPLICATE_SAME_ACCESS) && !handle.is_null() {
                Some(handle)
            } else { None }
        }
    }

    #[inline]
    pub fn enum_thread<'a>(&'a self) -> impl Iterator<Item=THREADENTRY32> + 'a {
        enum_thread().filter(move |x| x.pid() == self.pid)
    }

    #[inline]
    pub fn enum_module(&self) -> ToolHelperIter<MODULEENTRY32W> { enum_module(self.pid) }

    /// Wrapper of QueryFullProcessImageNameW
    pub fn image_path(&self) -> Option<String> {
        unsafe {
            let mut path = [0 as u16; MAX_PATH];
            let mut size = path.len() as u32;
            if QueryFullProcessImageNameW(*self.handle, 0, path.as_mut_ptr(), &mut size) > 0 {
                Some(path.as_ref().to_utf8())
            } else { None }
        }
    }

    fn read_unicode_string(&self, address: Address) -> Option<String> {
        self.read_value::<UNICODE_STRING>(address)
            .and_then(|u| self.read_wstring(u.Buffer as usize, u.Length as usize / 2))
    }

    pub fn cmdline(&self) -> Option<String> {
        use ntapi::FIELD_OFFSET;
        use ntapi::ntpebteb::PEB;
        use ntapi::ntrtl::RTL_USER_PROCESS_PARAMETERS;

        self.peb().and_then(|peb|
            self.read_value::<usize>(peb as usize + FIELD_OFFSET!(PEB, ProcessParameters))
        ).and_then(|p|
            self.read_unicode_string(p + FIELD_OFFSET!(RTL_USER_PROCESS_PARAMETERS, CommandLine))
        )
    }

    pub fn protect_memory(&self, address: usize, size: usize, attr: u32) -> Option<u32> {
        unsafe {
            let mut oldattr = 0u32;
            let r = VirtualProtectEx(*self.handle, address as LPVOID, size, attr, &mut oldattr);
            if r > 0 { Some(oldattr) } else { None }
        }
    }

    #[inline]
    pub fn write_memory(&self, address: usize, data: &[u8]) -> usize {
        write_process_memory(*self.handle, address, data)
    }

    pub fn write_code(&self, address: usize, data: &[u8]) -> usize {
        let r = write_process_memory(*self.handle, address, data);
        unsafe {
            FlushInstructionCache(*self.handle, address as LPCVOID, data.len());
        }
        return r;
    }

    pub fn write_cstr(&self, address: usize, s: String) -> bool {
        let b = self.write_memory(address, s.as_bytes()) == s.len();
        return b && self.write(address + s.len(), &0u8);
    }

    pub fn write<T>(&self, address: usize, val: &T) -> bool {
        unsafe {
            let size = size_of::<T>();
            let pdata: *mut u8 = transmute(val);
            let data = from_raw_parts(pdata, size);
            self.write_memory(address, data) == size
        }
    }

    pub fn enum_memory(&self, address: usize) -> MemoryIter {
        MemoryIter {process: self, address: address}
    }

    pub fn virtual_alloc(&self, address: usize, size: usize, mem_type: u32, protect: u32) -> usize {
        unsafe {
            VirtualAllocEx(*self.handle, address as LPVOID, size, mem_type, protect) as usize
        }
    }

    pub fn virtual_free(&self, address: usize) -> bool {
        unsafe {
            VirtualFreeEx(*self.handle, address as LPVOID, 0, MEM_RELEASE) > 0
        }
    }

    pub fn virtual_query(&self, address: usize) -> Option<MemoryPage> {
        unsafe {
            let mut mbi: MEMORY_BASIC_INFORMATION = zeroed();
            match VirtualQueryEx(*self.handle, address as LPVOID, &mut mbi, size_of_val(&mbi)) {
                0 => None, _ => Some(MemoryPage::from_mbi(&mbi)),
            }
        }
    }

    #[inline]
    pub fn terminate(&self) -> bool {
        unsafe { TerminateProcess(*self.handle, 0) > 0 }
    }

    pub fn get_exit_code(&self) -> Option<u32> {
        let mut code = 0u32;
        unsafe {
            if GetExitCodeProcess(*self.handle, &mut code) > 0 {
                Some(code)
            } else { None }
        }
    }

    pub fn dump_process(&self, path: &str, dump_type: DumpType) -> Result<(), WmError> {
        use self::ffi::*;

        unsafe extern "system" fn callback(_p: *const c_void, input: *const MINIDUMP_CALLBACK_INPUT, output: *mut MINIDUMP_CALLBACK_OUTPUT) -> BOOL {
            if input.is_null() || output.is_null() { return FALSE; }
            else if IncludeVmRegionCallback == (*input).CallbackType { (*output).Continue = TRUE; }

            TRUE
        }

        unsafe {
            let mci = MINIDUMP_CALLBACK_INFORMATION {
                CallbackRoutine: callback, CallbackParam: core::ptr::null()
            };
            let file = Handle(CreateFileW(
                path.to_unicode().as_ptr(),
                GENERIC_READ | GENERIC_WRITE,
                0, null_mut(), CREATE_ALWAYS,
                FILE_ATTRIBUTE_NORMAL, null_mut()
            ));
            if !file.success() { return Err(WmError::CreateFile); }

            if MiniDumpWriteDump(*self.handle, self.pid, file.0, match dump_type {
                DumpType::Mini => MiniDumpNormal,
                DumpType::Full => MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithUnloadedModules |
                                  MiniDumpWithUnloadedModules | MiniDumpWithProcessThreadData |
                                  MiniDumpWithFullMemoryInfo | MiniDumpWithThreadInfo |
                                  MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory |
                                  MiniDumpWithTokenInformation,
            }, None, null_mut(), &mci) == 0 { WmError::last_result() } else { Ok(()) }
        }
    }

    pub fn each_symbol(&self, module: usize, callback: &dyn FnMut(usize, Arc<str>) -> bool) -> bool {
        // const SYMENUM_OPTIONS_DEFAULT: u32 = 1;
        unsafe extern "system" fn enum_proc(si: *mut SYMBOL_INFOW, _size: ULONG, arg: PVOID) -> BOOL {
            let si = &*si;
            let callback: *mut &'static mut dyn FnMut(usize, Arc<str>) -> bool = transmute(arg);
            let name: Arc<str> = from_raw_parts(si.Name.as_ptr(), si.NameLen as usize).to_utf8().into();
            (*callback)(si.Address as usize, name) as BOOL
        }
        unsafe {
            SymEnumSymbolsW(
                *self.handle,
                module as u64,
                transmute(b"*\0\0\0".as_ptr()),
                Some(enum_proc),
                transmute(&callback)
            ) > 0
        }
    }
}

pub fn sym_get_options() -> u32 { unsafe { SymGetOptions() } }

pub fn sym_set_options(option: u32) -> u32 { unsafe { SymSetOptions(option) } }

pub fn this_process() -> &'static Process {
    static mut P: Option<Process> = None;

    unsafe { P.get_or_insert_with(Process::current) }
}

pub trait DbgReg {
    fn set_step(&mut self, step: bool);
    fn set_rf(&mut self);
    fn test_eflags(&self, flag: u32) -> bool;

    fn l_enable(&mut self, enable: bool);
    fn set_local(&mut self, idx: usize, set: bool);
    fn set_rw(&mut self, idx: usize, val: reg_t);
    fn set_len(&mut self, idx: usize, val: reg_t);

    fn empty(&self) -> bool;

    fn set_bp(&mut self, address: reg_t, idx: usize, rw: reg_t, len: reg_t);
    fn unset_bp(&mut self, idx: usize);
}

#[inline(always)]
fn set_bit(n: &mut reg_t, x: usize, set: bool) {
    if set { *n |= 1 << x; } else { *n &= !(1 << x); }
}

#[inline(always)]
fn test_bit(n: reg_t, x: usize) -> bool { n & (1 << x) > 0 }

#[inline(always)]
fn set_bit2(n: &mut reg_t, x: usize, v: reg_t) {
    *n &= !(0b11 << x); *n |= v << x;
}

const L0: usize = 0;
const G0: usize = 1;
const L1: usize = 2;
const G1: usize = 3;
const L2: usize = 4;
const G2: usize = 5;
const L3: usize = 5;
const G3: usize = 7;
const L_ENABLE: usize = 8;
const G_ENABLE: usize = 9;
const RW0: usize = 16;
const LEN0: usize = 18;
const RW1: usize = 20;
const LEN1: usize = 22;
const RW2: usize = 24;
const LEN2: usize = 26;
const RW3: usize = 28;
const LEN3: usize = 30;

// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-context
#[cfg(target_arch = "x86_64")]
impl AbstractRegs for CONTEXT {
    fn ip(&mut self) -> &mut reg_t { &mut self.Rip }
    fn sp(&mut self) -> &mut reg_t { &mut self.Rsp }
}

#[cfg(target_arch = "x86")]
impl AbstractRegs for CONTEXT {
    fn ip(&mut self) -> &mut reg_t { &mut self.Eip }
    fn sp(&mut self) -> &mut reg_t { &mut self.Esp }
}

impl DbgReg for CONTEXT {
    fn set_step(&mut self, step: bool) {
        let flags = self.EFlags;
        self.EFlags = if step { flags | EFLAGS_TF } else { flags & (!EFLAGS_TF) };
    }

    fn set_rf(&mut self) { self.EFlags |= EFLAGS_RF; }

    fn test_eflags(&self, flag: u32) -> bool {
        self.EFlags & flag > 0
    }

    fn l_enable(&mut self, enable: bool) {
        set_bit(&mut self.Dr7, L_ENABLE, enable);
    }

    fn set_local(&mut self, idx: usize, set: bool) {
        let x = match idx {
            0 => L0,
            1 => L1,
            2 => L2,
            _ => L3,
        };
        set_bit(&mut self.Dr7, x, set);
    }

    fn set_rw(&mut self, idx: usize, val: reg_t) {
        let x = match idx {
            0 => RW0,
            1 => RW1,
            2 => RW2,
            _ => RW3,
        };
        set_bit2(&mut self.Dr7, x, val);
    }

    fn set_len(&mut self, idx: usize, val: reg_t) {
        let x = match idx {
            0 => LEN0,
            1 => LEN1,
            2 => LEN2,
            _ => LEN3,
        };
        set_bit2(&mut self.Dr7, x, val);
    }

    fn empty(&self) -> bool {
        let n = self.Dr7;
        !test_bit(n, L0) && !test_bit(n, L1) && !test_bit(n, L2) && !test_bit(n, L3)
    }

    fn set_bp(&mut self, address: reg_t, idx: usize, rw: reg_t, len: reg_t) {
        self.l_enable(true);
        self.set_local(idx, true);
        self.set_rw(idx, rw);
        self.set_len(idx, len);
        match idx {
            0 => self.Dr0 = address,
            1 => self.Dr1 = address,
            2 => self.Dr2 = address,
            _ => self.Dr3 = address,
        };
    }

    fn unset_bp(&mut self, idx: usize) {
        self.set_local(idx, false);
        self.set_rw(idx, 0);
        self.set_len(idx, 0);
        match idx {
            0 => self.Dr0 = 0,
            1 => self.Dr1 = 0,
            2 => self.Dr2 = 0,
            _ => self.Dr3 = 0,
        };
        if self.empty() { self.l_enable(false); }
    }
}

pub fn is_32(pid: pid_t) -> bool {
    Process::open(pid, Some(PROCESS_QUERY_INFORMATION)).map(|p| p.is_wow64()).unwrap_or(false)
}

pub fn win32_exception_name(code: u32) -> &'static str {
    match code {
        0x00000000 => "STATUS_WAIT_0",
        0x00000080 => "STATUS_ABANDONED_WAIT_0",
        0x000000C0 => "STATUS_USER_APC",
        0x00000102 => "STATUS_TIMEOUT",
        0x00000103 => "STATUS_PENDING",
        0x00010001 => "DBG_EXCEPTION_HANDLED",
        0x00010002 => "DBG_CONTINUE",
        0x40000005 => "STATUS_SEGMENT_NOTIFICATION",
        0x40000015 => "STATUS_FATAL_APP_EXIT",
        0x40010001 => "DBG_REPLY_LATER",
        0x40010003 => "DBG_TERMINATE_THREAD",
        0x40010004 => "DBG_TERMINATE_PROCESS",
        0x40010005 => "DBG_CONTROL_C",
        0x40010006 => "DBG_PRINTEXCEPTION_C",
        0x40010007 => "DBG_RIPEXCEPTION",
        0x40010008 => "DBG_CONTROL_BREAK",
        0x40010009 => "DBG_COMMAND_EXCEPTION",
        0x4001000A => "DBG_PRINTEXCEPTION_WIDE_C",
        0x80000001 => "GUARD_PAGE_VIOLATION",
        0x80000002 => "DATATYPE_MISALIGNMENT",
        0x80000003 => "BREAKPOINT",
        0x80000004 => "SINGLE_STEP",
        0x80000026 => "LONGJUMP",
        0x80000029 => "UNWIND_CONSOLIDATE",
        0x80010001 => "DBG_EXCEPTION_NOT_HANDLED",
        0xC0000005 => "ACCESS_VIOLATION",
        0xC0000006 => "IN_PAGE_ERROR",
        0xC0000008 => "INVALID_HANDLE",
        0xC000000D => "INVALID_PARAMETER",
        0xC0000017 => "NO_MEMORY",
        0xC000001D => "ILLEGAL_INSTRUCTION",
        0xC0000025 => "NONCONTINUABLE_EXCEPTION",
        0xC0000026 => "INVALID_DISPOSITION",
        0xC000008C => "ARRAY_BOUNDS_EXCEEDED",
        0xC000008D => "FLOAT_DENORMAL_OPERAND",
        0xC000008E => "FLOAT_DIVIDE_BY_ZERO",
        0xC000008F => "FLOAT_INEXACT_RESULT",
        0xC0000090 => "FLOAT_INVALID_OPERATION",
        0xC0000091 => "FLOAT_OVERFLOW",
        0xC0000092 => "FLOAT_STACK_CHECK",
        0xC0000093 => "FLOAT_UNDERFLOW",
        0xC0000094 => "INTEGER_DIVIDE_BY_ZERO",
        0xC0000095 => "INTEGER_OVERFLOW",
        0xC0000096 => "PRIVILEGED_INSTRUCTION",
        0xC00000FD => "STACK_OVERFLOW",
        0xC0000135 => "DLL_NOT_FOUND",
        0xC0000138 => "ORDINAL_NOT_FOUND",
        0xC0000139 => "ENTRYPOINT_NOT_FOUND",
        0xC000013A => "CONTROL_C_EXIT",
        0xC0000142 => "DLL_INIT_FAILED",
        0xC00002B4 => "FLOAT_MULTIPLE_FAULTS",
        0xC00002B5 => "FLOAT_MULTIPLE_TRAPS",
        0xC00002C9 => "REG_NAT_CONSUMPTION",
        0xC0000374 => "HEAP_CORRUPTION",
        0xC0000409 => "STACK_BUFFER_OVERRUN",
        0xC0000417 => "INVALID_CRUNTIME_PARAMETER",
        0xC0000420 => "ASSERTION_FAILURE",
        0xC00004A2 => "ENCLAVE_VIOLATION",
        // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/specific-exceptions
        0xCFFFFFFF => "APPLICATION_HANG",
        0xE06D7363 => "CPP_EH_EXCEPTION",
        0xE0434f4D => "CLR_EXCEPTION",
        0x6ba => "RPC_S_SERVER_UNAVAILABLE",
        _ => "",
    }
}

pub fn get_exception_name(code: u32) -> String {
    win32_exception_name(code).to_string()
}

pub fn normalize_path(mut path: String) -> String {
    const PREFIX: &str = r#"\SystemRoot"#;
    const PR2: &str = r#"\??\"#;
    const PR3: &str = r#"\\?\"#;

    if path.starts_with(PREFIX) {
        // TODO: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getwindowsdirectorya
        path.replace_range(..PREFIX.len(), "C:\\Windows");
    }
    if path.starts_with(PR2) {
        path.replace_range(..PR2.len(), "");
    }
    if path.starts_with(PR3) {
        path.replace_range(..PR3.len(), "");
    }
    path
}

pub trait IntoThreadProc {
    fn into_thread_fn(self) -> (LPTHREAD_START_ROUTINE, LPVOID);
}

impl<F: FnOnce()> IntoThreadProc for F {
    fn into_thread_fn(self) -> (LPTHREAD_START_ROUTINE, LPVOID) {
        unsafe extern "system" fn wrapper(p: LPVOID) -> u32 {
            let closure: Box<Box<dyn FnOnce()>> = Box::from_raw(transmute(p));
            closure(); 0
        }
        let closure: Box<dyn FnOnce()> = Box::new(self);
        (Some(wrapper), Box::into_raw(Box::new(closure)) as LPVOID)
    }
}

pub fn create_thread(proc: impl IntoThreadProc) -> Option<(HANDLE, u32)> {
    unsafe {
        let mut id: DWORD = 0;
        let (f, p) = proc.into_thread_fn();
        let handle = CreateThread(null_mut(), 0, f, p, 0, &mut id);
        if handle.is_null() { None } else { Some((handle, id)) }
    }
}